#!/bin/bash

# Redis Cluster Production Backup Script
# =====================================

set -e

# Configuration
REDIS_PASSWORD="Redis@2024#Prod"
REDIS_NODES=(
    "173.18.0.11:6379"
    "173.18.0.12:6379"
    "173.18.0.13:6379"
    "173.18.0.14:6379"
    "173.18.0.15:6379"
    "173.18.0.16:6379"
)

# Backup configuration
BACKUP_DIR="/var/backups/redis-cluster"
RETENTION_DAYS=30
BACKUP_TYPE="full"  # full or incremental
COMPRESS_BACKUP=true
BACKUP_FORMAT="tar.gz"  # tar.gz or zip

# Container names
CONTAINER_NAMES=(
    "redis-6001"
    "redis-6002"
    "redis-6003"
    "redis-6004"
    "redis-6005"
    "redis-6006"
)

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Logging function
log() {
    echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
}

warn() {
    echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
}

error() {
    echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
}

info() {
    echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
}

# Create backup directory
create_backup_dir() {
    local timestamp=$(date +"%Y%m%d_%H%M%S")
    local backup_path="$BACKUP_DIR/redis_backup_$timestamp"
    
    mkdir -p "$backup_path"
    echo "$backup_path"
}

# Check if Redis node is reachable
check_node_connectivity() {
    local node=$1
    
    if redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD ping &> /dev/null; then
        return 0
    else
        return 1
    fi
}

# Check cluster health before backup
check_cluster_health() {
    local node=${REDIS_NODES[0]}
    local cluster_info
    
    cluster_info=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD cluster info 2>/dev/null || echo "")
    
    if [[ $cluster_info == *"cluster_state:ok"* ]]; then
        log "✓ Cluster is healthy, proceeding with backup"
        return 0
    else
        error "✗ Cluster is not healthy, aborting backup"
        return 1
    fi
}

# Create RDB backup for a single node
backup_node_rdb() {
    local node=$1
    local container_name=$2
    local backup_path=$3
    local node_name="redis-${node%:*}"
    
    log "Creating RDB backup for $node_name"
    
    # Check node connectivity
    if ! check_node_connectivity $node; then
        error "Node $node_name is not reachable, skipping backup"
        return 1
    fi
    
    # Trigger background save
    local lastsave_before=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD lastsave)
    redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD bgsave &> /dev/null
    
    # Wait for background save to complete
    local max_wait=300  # 5 minutes max wait
    local wait_time=0
    
    while [ $wait_time -lt $max_wait ]; do
        local lastsave_after=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD lastsave)
        if [ "$lastsave_after" -gt "$lastsave_before" ]; then
            break
        fi
        sleep 2
        ((wait_time += 2))
        echo -n "."
    done
    
    if [ $wait_time -ge $max_wait ]; then
        error "Background save timeout for $node_name"
        return 1
    fi
    
    echo " ✓"
    
    # Copy RDB file from container
    local node_backup_dir="$backup_path/$node_name"
    mkdir -p "$node_backup_dir"
    
    if docker cp "$container_name:/data/dump.rdb" "$node_backup_dir/" 2>/dev/null; then
        log "✓ RDB backup completed for $node_name"
        
        # Copy AOF file if it exists
        if docker exec "$container_name" test -f /data/appendonly.aof 2>/dev/null; then
            docker cp "$container_name:/data/appendonly.aof" "$node_backup_dir/" 2>/dev/null
            log "✓ AOF backup completed for $node_name"
        fi
        
        # Copy cluster configuration
        if docker exec "$container_name" test -f /data/nodes.conf 2>/dev/null; then
            docker cp "$container_name:/data/nodes.conf" "$node_backup_dir/" 2>/dev/null
            log "✓ Cluster config backup completed for $node_name"
        fi
        
        return 0
    else
        error "✗ Failed to copy RDB file for $node_name"
        return 1
    fi
}

# Create logical backup using redis-cli
backup_node_logical() {
    local node=$1
    local backup_path=$2
    local node_name="redis-${node%:*}"
    
    log "Creating logical backup for $node_name"
    
    # Check node connectivity
    if ! check_node_connectivity $node; then
        error "Node $node_name is not reachable, skipping logical backup"
        return 1
    fi
    
    local node_backup_dir="$backup_path/$node_name"
    mkdir -p "$node_backup_dir"
    
    # Get all keys and create logical backup
    local backup_file="$node_backup_dir/redis_backup.json"
    
    # Use redis-cli --scan to get all keys
    {
        echo "{"
        echo "  \"timestamp\": \"$(date -Iseconds)\","
        echo "  \"node\": \"$node\","
        echo "  \"data\": {"
        
        local first_key=true
        redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD --scan | while read -r key; do
            if [ -n "$key" ]; then
                if [ "$first_key" = true ]; then
                    first_key=false
                else
                    echo ","
                fi
                
                # Get key type and value
                local key_type=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD type "$key" 2>/dev/null)
                local ttl=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD ttl "$key" 2>/dev/null)
                
                echo -n "    \"$key\": {"
                echo -n "\"type\": \"$key_type\","
                echo -n "\"ttl\": $ttl,"
                
                case $key_type in
                    "string")
                        local value=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD --raw get "$key" 2>/dev/null | sed 's/"/\\"/g' | tr '\n' ' ')
                        echo -n "\"value\": \"$value\""
                        ;;
                    "list")
                        local value=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD --raw lrange "$key" 0 -1 2>/dev/null | sed 's/"/\\"/g' | tr '\n' ' ')
                        echo -n "\"value\": [$value]"
                        ;;
                    "set")
                        local value=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD --raw smembers "$key" 2>/dev/null | sed 's/"/\\"/g' | tr '\n' ' ')
                        echo -n "\"value\": [$value]"
                        ;;
                    "zset")
                        local value=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD --raw zrange "$key" 0 -1 withscores 2>/dev/null | sed 's/"/\\"/g' | tr '\n' ' ')
                        echo -n "\"value\": [$value]"
                        ;;
                    "hash")
                        local value=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD --raw hgetall "$key" 2>/dev/null | sed 's/"/\\"/g' | tr '\n' ' ')
                        echo -n "\"value\": {$value}"
                        ;;
                    *)
                        echo -n "\"value\": null"
                        ;;
                esac
                
                echo -n "}"
            fi
        done
        
        echo ""
        echo "  }"
        echo "}"
    } > "$backup_file"
    
    if [ -f "$backup_file" ] && [ -s "$backup_file" ]; then
        log "✓ Logical backup completed for $node_name"
        return 0
    else
        error "✗ Failed to create logical backup for $node_name"
        return 1
    fi
}

# Compress backup
compress_backup() {
    local backup_path=$1
    local archive_name
    
    if [ "$BACKUP_FORMAT" = "zip" ]; then
        archive_name="${backup_path}.zip"
        if command -v zip &> /dev/null; then
            (cd "$BACKUP_DIR" && zip -r "$archive_name" "$(basename "$backup_path")")
            rm -rf "$backup_path"
            log "✓ Backup compressed to $archive_name"
        else
            warn "zip command not found, skipping compression"
            return 1
        fi
    else
        archive_name="${backup_path}.tar.gz"
        if command -v tar &> /dev/null; then
            tar -czf "$archive_name" -C "$BACKUP_DIR" "$(basename "$backup_path")"
            rm -rf "$backup_path"
            log "✓ Backup compressed to $archive_name"
        else
            warn "tar command not found, skipping compression"
            return 1
        fi
    fi
    
    echo "$archive_name"
}

# Cleanup old backups
cleanup_old_backups() {
    log "Cleaning up backups older than $RETENTION_DAYS days"
    
    local deleted_count=0
    
    if [ "$BACKUP_FORMAT" = "zip" ]; then
        deleted_count=$(find "$BACKUP_DIR" -name "redis_backup_*.zip" -type f -mtime +$RETENTION_DAYS -delete -print | wc -l)
    else
        deleted_count=$(find "$BACKUP_DIR" -name "redis_backup_*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete -print | wc -l)
    fi
    
    log "✓ Deleted $deleted_count old backup files"
}

# Verify backup integrity
verify_backup() {
    local archive_path=$1
    
    log "Verifying backup integrity: $archive_path"
    
    if [ "$BACKUP_FORMAT" = "zip" ]; then
        if command -v unzip &> /dev/null; then
            if unzip -t "$archive_path" &> /dev/null; then
                log "✓ Backup integrity verified"
                return 0
            else
                error "✗ Backup integrity check failed"
                return 1
            fi
        else
            warn "unzip command not found, skipping integrity check"
            return 0
        fi
    else
        if command -v tar &> /dev/null; then
            if tar -tzf "$archive_path" &> /dev/null; then
                log "✓ Backup integrity verified"
                return 0
            else
                error "✗ Backup integrity check failed"
                return 1
            fi
        else
            warn "tar command not found, skipping integrity check"
            return 0
        fi
    fi
}

# Generate backup report
generate_backup_report() {
    local archive_path=$1
    local start_time=$2
    local end_time=$3
    local backup_type=$4
    
    local report_file="${archive_path%.*}_report.txt"
    
    {
        echo "Redis Cluster Backup Report"
        echo "==========================="
        echo "Backup Type: $backup_type"
        echo "Start Time: $start_time"
        echo "End Time: $end_time"
        echo "Duration: $(date -d@$((end_time - start_time)) -u +%H:%M:%S)"
        echo "Archive Path: $archive_path"
        echo "Archive Size: $(du -h "$archive_path" | cut -f1)"
        echo ""
        echo "Cluster Information:"
        echo "-------------------"
        
        local node=${REDIS_NODES[0]}
        local cluster_info=$(redis-cli -h ${node%:*} -p ${node#*:} -a $REDIS_PASSWORD cluster info 2>/dev/null || echo "")
        
        echo "$cluster_info" | while IFS= read -r line; do
            echo "  $line"
        done
        
        echo ""
        echo "Node Status:"
        echo "-----------"
        
        for i in "${!REDIS_NODES[@]}"; do
            local node=${REDIS_NODES[$i]}
            local container_name=${CONTAINER_NAMES[$i]}
            local node_name="redis-${node%:*}"
            
            if check_node_connectivity $node; then
                echo "  ✓ $node_name - Connected"
            else
                echo "  ✗ $node_name - Disconnected"
            fi
        done
        
        echo ""
        echo "Generated: $(date)"
    } > "$report_file"
    
    log "✓ Backup report generated: $report_file"
}

# Main backup function
main() {
    local backup_type=${1:-$BACKUP_TYPE}
    
    log "Starting Redis Cluster Backup"
    log "============================="
    
    local start_time=$(date +%s)
    local start_time_readable=$(date -Iseconds)
    
    # Check prerequisites
    if ! command -v redis-cli &> /dev/null; then
        error "redis-cli is not installed or not in PATH"
        exit 1
    fi
    
    if ! command -v docker &> /dev/null; then
        error "docker is not installed or not in PATH"
        exit 1
    fi
    
    # Create backup directory
    local backup_path=$(create_backup_dir)
    log "Backup directory: $backup_path"
    
    # Check cluster health
    if ! check_cluster_health; then
        exit 1
    fi
    
    # Perform backup based on type
    local success_count=0
    local total_count=${#REDIS_NODES[@]}
    
    case $backup_type in
        "rdb"|"full")
            log "Performing RDB backup for all nodes"
            for i in "${!REDIS_NODES[@]}"; do
                if backup_node_rdb "${REDIS_NODES[$i]}" "${CONTAINER_NAMES[$i]}" "$backup_path"; then
                    ((success_count++))
                fi
            done
            ;;
        "logical")
            log "Performing logical backup for all nodes"
            for node in "${REDIS_NODES[@]}"; do
                if backup_node_logical "$node" "$backup_path"; then
                    ((success_count++))
                fi
            done
            ;;
        "both")
            log "Performing both RDB and logical backup"
            for i in "${!REDIS_NODES[@]}"; do
                if backup_node_rdb "${REDIS_NODES[$i]}" "${CONTAINER_NAMES[$i]}" "$backup_path"; then
                    ((success_count++))
                fi
            done
            
            for node in "${REDIS_NODES[@]}"; do
                backup_node_logical "$node" "$backup_path"
            done
            ;;
        *)
            error "Unknown backup type: $backup_type"
            echo "Supported types: rdb, logical, both"
            exit 1
            ;;
    esac
    
    if [ $success_count -eq 0 ]; then
        error "No successful backups created"
        exit 1
    fi
    
    log "Backup completed: $success_count/$total_count nodes"
    
    # Compress backup if requested
    local archive_path="$backup_path"
    if [ "$COMPRESS_BACKUP" = true ]; then
        archive_path=$(compress_backup "$backup_path")
        if [ $? -ne 0 ]; then
            archive_path="$backup_path"
        fi
    fi
    
    # Verify backup integrity
    verify_backup "$archive_path"
    
    # Generate backup report
    local end_time=$(date +%s)
    local end_time_readable=$(date -Iseconds)
    generate_backup_report "$archive_path" "$start_time_readable" "$end_time_readable" "$backup_type"
    
    # Cleanup old backups
    cleanup_old_backups
    
    log "Backup process completed successfully!"
    log "Archive: $archive_path"
    
    # Display summary
    echo ""
    echo "Backup Summary:"
    echo "==============="
    echo "Type: $backup_type"
    echo "Nodes: $success_count/$total_count"
    echo "Archive: $archive_path"
    echo "Size: $(du -h "$archive_path" | cut -f1)"
    echo "Duration: $(date -d@$((end_time - start_time)) -u +%H:%M:%S)"
}

# Show usage
usage() {
    echo "Usage: $0 [backup_type]"
    echo "Backup types:"
    echo "  rdb      - RDB snapshot backup (default)"
    echo "  logical  - Logical backup using redis-cli"
    echo "  both     - Both RDB and logical backup"
    echo ""
    echo "Examples:"
    echo "  $0              # RDB backup"
    echo "  $0 rdb          # RDB backup"
    echo "  $0 logical      # Logical backup"
    echo "  $0 both         # Both backup types"
}

# Check arguments
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
    usage
    exit 0
fi

# Run main function
main "$@"